﻿using System;
using System.Globalization;
using System.Reflection;
using Microsoft.Practices.CompositeUI.Properties;

namespace Microsoft.Practices.CompositeUI.EventBroker
{
    /// <summary>
    /// 	Represents a publication for a topic and handles the publication event.
    /// </summary>
    internal class Publication : IDisposable
    {
        #region Fields & Properties

        private readonly EventTopic _topic;

        private readonly WeakReference _weakPublisher;

        /// <summary>
        /// 	The publisher of the event.
        /// </summary>
        public object Publisher
        {
            get
            {
                return _weakPublisher.Target;
            }
        }

        private readonly string _eventName;

        /// <summary>
        /// 	The name of the event on the <see cref = "Publication.Publisher" />.
        /// </summary>
        public string EventName
        {
            get
            {
                return _eventName;
            }
        }

        private readonly WeakReference _weakWorkItem;

        /// <summary>
        /// 	The <see cref = "WorkItem" /> this Publication lives in.
        /// </summary>
        public WorkItem WorkItem
        {
            get
            {
                return (WorkItem) _weakWorkItem.Target;
            }
        }

        private readonly PublicationScope _scope;

        /// <summary>
        /// 	The <see cref = "PublicationScope" /> of the event.
        /// </summary>
        public PublicationScope Scope
        {
            get
            {
                return _scope;
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// 	Initializes a new instance of the <see cref = "Publication" /> class
        /// </summary>
        public Publication(EventTopic topic, object publisher, string eventName, WorkItem workItem, PublicationScope scope)
        {
            _topic = topic;
            _weakPublisher = new WeakReference(publisher);
            _eventName = eventName;
            _weakWorkItem = new WeakReference(workItem);
            _scope = scope;

            var publishedEvent = publisher.GetType().GetEvent(eventName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
            if (publishedEvent == null)
            {
                throw new EventBrokerException(String.Format(CultureInfo.CurrentCulture, Resources.CannotFindPublishedEvent, eventName));
            }
            ThrowIfInvalidEventHandler(publishedEvent);
            ThrowIfEventIsStatic(publishedEvent);

            publishedEvent.AddEventHandler(publisher, Delegate.CreateDelegate(publishedEvent.EventHandlerType, this, GetType().GetMethod("PublicationHandler")));
        }

        private void ThrowIfEventIsStatic(EventInfo publishedEvent)
        {
            if (publishedEvent.GetAddMethod().IsStatic || publishedEvent.GetRemoveMethod().IsStatic)
            {
                throw new EventBrokerException(String.Format(CultureInfo.CurrentCulture, Resources.StaticPublisherNotAllowed, EventName));
            }
        }

        private void ThrowIfInvalidEventHandler(EventInfo info)
        {
            if (typeof (EventHandler).IsAssignableFrom(info.EventHandlerType) || (info.EventHandlerType.IsGenericType && typeof (EventHandler<>).IsAssignableFrom(info.EventHandlerType.GetGenericTypeDefinition())))
            {
                return;
            }
            throw new EventBrokerException(String.Format(CultureInfo.CurrentCulture, Resources.InvalidPublicationSignature, info.DeclaringType.FullName, info.Name));
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        /// 	Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// 	Implementation of the disposable pattern.
        /// </summary>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                var publisher = Publisher;
                if (publisher != null)
                {
                    var publishedEvent = publisher.GetType().GetEvent(_eventName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
                    if (publishedEvent != null)
                    {
                        publishedEvent.RemoveEventHandler(publisher, Delegate.CreateDelegate(publishedEvent.EventHandlerType, this, GetType().GetMethod("PublicationHandler")));
                    }
                }
            }
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// 	Fires the event publication.
        /// </summary>
        public void PublicationHandler(object sender, EventArgs e)
        {
            _topic.Fire(this, sender, e);
        }

        #endregion
    }
}